import { ResUtils } from './../utils/ResUtils';
import { ButtonClick, ButtonClickParams } from './button/ButtonClick';
import * as cc from "cc";
import NodeUtils from "../utils/NodeUtils";
import { ButtonLongPress, ButtonLongPressParams } from "./button/ButtonLongPress";
import { IEventHandler } from '../event/IEventHandler';
import { CompositeDisposable } from '../event/EventKit';
import { Loger } from '../loger/Loger';

const { ccclass, property } = cc._decorator;

@ccclass
export default class BaseComponent extends cc.Component implements IEventHandler {
    private arrClickNode: { node: cc.Node, target: any }[] = [];
    private mapClickId: Map<string, number> = new Map();

    public uiTrans: cc.UITransform = null;
    public parentUiTrans: cc.UITransform = null;
    disposable: CompositeDisposable;
    private dependAssetMap: { [uuid: string]: [cc.Asset, number] } = {};
    disposeEvent(): void {
        this.disposable && this.disposable.dispose();
        this.disposable = null;
    }
    /**注册事件监听 */
    registEvent(): void {
        //子类重写
    }

    protected onLoad() {
        this.uiTrans = this.node.getComponent(cc.UITransform);
        this.parentUiTrans = this.node.parent.getComponent(cc.UITransform);
    }

    protected start() {

    }

    protected onDestroy() {
        this.disposeEvent();
        this.releaseCache();
    }

    /**
     * 长按
     * @param node 按钮
     * @param clickListener 连续回调 once为true,只触发一次,并且endClickListener 不再回调
     * @param endClickListener 结束回调 没触发连续回调时,松开按钮回调一次
     * @param target 回调绑定目标对象
     * @param interval 触发长按间隔
     * @param once 是否一次性回调,true
     */
    protected setLongPressListener(node: cc.Node | cc.Component, clickListener: (ev: cc.EventTouch) => void, endClickListener: (ev: cc.EventTouch) => void, target?: any, interval: number = 0.5, once: boolean = false) {
        this._setLongPressListener(node, clickListener, endClickListener, true, target, interval, once);
    }

    /**
     * 长按
     * @param node 按钮
     * @param clickListener 连续回调 once为true,只触发一次,并且endClickListener 不再回调
     * @param endClickListener 结束回调 没触发连续回调时,松开按钮回调一次
     * @param target 回调绑定目标对象
     * @param interval 触发长按间隔
     * @param once 是否一次性回调,true
     */
    protected setLongPressListenerNoAnim(node: cc.Node | cc.Component, clickListener: (ev: cc.EventTouch) => void, endClickListener: (ev: cc.EventTouch) => void, target?: any, interval: number = 0.5, once: boolean = false) {
        this._setLongPressListener(node, clickListener, endClickListener, false, target, interval, once);
    }

    protected setClickListenerNoAnim(node: cc.Node | cc.Component, clickListener: (ev: cc.EventTouch) => void, target?: any, checkClickAble: boolean = true) {
        this._setClickListener(node, clickListener, false, target, checkClickAble)
    }

    protected setClickListener(node: cc.Node | cc.Component, clickListener: (ev: cc.EventTouch) => void, target?: any, checkClickAble: boolean = true) {
        this._setClickListener(node, clickListener, true, target, checkClickAble)
    }

    /**
     * @desc 给节点添加点击事件
     * @param node 按钮节点
     * @param anim 是否播放点击动画
     * @param clickListener 点击回调
     * @param target 事件绑定目标
     * @param checkClickAble 是否检查点击过快
     * @returns 
     */
    private _setClickListener(node: cc.Node | cc.Component, clickListener: (ev: cc.EventTouch) => void, anim: boolean, target?: any, checkClickAble: boolean = true) {
        let nd = (node instanceof cc.Component) ? node.node : node
        if (!(nd instanceof cc.Node)) {
            Loger.logView("setClickListener node param incorrect!", node)
            return
        }
        target = target ? target : this;
        let button = NodeUtils.getOrAddComponent<ButtonClick>(nd, ButtonClick);
        let data: ButtonClickParams = {
            /**点击回调 */
            clickListener: clickListener,
            /**是否播放点击动画,默认不播放 */
            anim: anim,
            /**事件绑定目标 */
            target: target,
            /**是否检查点击过快 */
            checkClickAble: checkClickAble
        }
        button.init(data);
        this._addClickNode(nd, target);
    }

    /**
     * 长按
     * @param node 按钮
     * @param clickListener 连续回调 once为true,只触发一次,并且endClickListener 不再回调
     * @param endClickListener 结束回调 没触发连续回调时,松开按钮回调一次
     * @param anim 是否播放点击动画,默认不播放
     * @param target 回调绑定目标对象
     * @param interval 触发长按间隔
     * @param once 是否一次性回调,true
     */
    private _setLongPressListener(node: cc.Node | cc.Component, clickListener: (ev: cc.EventTouch) => void, endClickListener: (ev: cc.EventTouch) => void, anim: boolean, target?: any, interval: number = 0.5, once: boolean = false) {
        let nd = (node instanceof cc.Component) ? node.node : node
        if (!(nd instanceof cc.Node)) {
            Loger.logView("setClickListener node param incorrect!", node)
            return
        }
        let longPress = NodeUtils.getOrAddComponent<ButtonLongPress>(nd, ButtonLongPress)
        let data: ButtonLongPressParams = {
            /**连续回调 once为true,只触发一次,并且endClickListener 不再回调 */
            callBack: clickListener,
            /**结束回调 没触发连续回调时,松开按钮回调一次 */
            endCallback: endClickListener,
            /**是否播放点击动画,默认不播放 */
            anim: anim,
            /**回调绑定目标对象 */
            target: target,
            /**触发长按间隔 */
            interval: interval,
            /**是否一次性回调,true */
            once: once
        }
        longPress.init(data);
        this._addClickNode(nd, target);
    }

    /**
     * @desc 缓存点击节点,用于统一管理
     */
    private _addClickNode(node: cc.Node, target: any) {
        if (this.mapClickId.get(node.uuid) !== undefined) {
            return;
        }
        const index = this.arrClickNode.push({ node: node, target: target });
        this.mapClickId.set(node.uuid, index);
    }

    protected removeClickListener(node: cc.Node | cc.Component, target?: any) {
        let nd = (node instanceof cc.Component) ? node.node : node
        if (!(nd instanceof cc.Node)) {
            Loger.log("setClickListener node param incorrect!", node)
            return
        }
        target = target ? target : this;
        nd.isValid && nd.targetOff(target);
    }

    protected removeAllClickListener() {
        if (this.arrClickNode && this.arrClickNode.length > 0) {
            this.arrClickNode.forEach(data => {
                this.removeClickListener(data.node, data.target);
            });
            this.arrClickNode = [];
            this.mapClickId.clear();
        }
    }

    /**
     * 动态加载图片
     * @param path 相对于bundle的路径
     * @param bundleName bundle名字 默认resources
     * @param useDefaultTexture 找不到图片时,是否用默认图片代替
     * @returns 
     */
    protected getSpriteFrame(path: string, bundleName: string = "resources", useDefaultTexture: boolean = true): Promise<cc.SpriteFrame> {
        return new Promise<cc.SpriteFrame>((resolve, reject) => {
            this.getRes(`${path}/spriteFrame`,null, bundleName).then((sp: cc.SpriteFrame) => {
                if (!this.node || !this.node.isValid || !sp) {
                    reject();
                    return;
                }
                resolve(sp)
            }).catch((err) => {
                if (useDefaultTexture) {
                    if (!this.node || !this.node.isValid) {
                        reject();
                        return;
                    }
                    this.getRes("framework/texture/default_sprite/spriteFrame").then((sp: cc.SpriteFrame) => {
                        if (!this.node || !this.node.isValid || !sp) {
                            reject();
                            return;
                        }
                        resolve(sp)
                    }).catch((err) => {
                        reject(err);
                    })
                } else {
                    reject(err);
                }
            })
        })
    }

    /**
     * 加载资源, 所有动态加载资源,都要通过BaseComponent来加载,方便统一管理
     * @param path 相对于bundle的路径
     * @param bundleName bundle名字 默认resources
     * @returns 
     */
    public getRes<T extends cc.Asset>(path: string, type = null, bundleName: string = "resources") {
        return new Promise<T>((resolve, reject) => {
            let res = this._getLoadedRes(path,type,bundleName)
            if (res) {
                resolve(res as T);
            } else {
                this._loadRes<T>(path,type,bundleName).then((res)=>{
                    resolve(res as T);                    
                }).catch((err)=>{
                    Loger.error(err);
                })
            }

        })
    }

    /**
     * 需要预加载的资源,返回路径数组 如果需要预加载,子类重写
     */
    protected getPreloadResPath(): string[] {
        return null;
    }
    /**
     * 预加载资源
     * @returns 
     */
    public async preloadRes() {
        let preloadList = this.getPreloadResPath();
        if (preloadList && preloadList.length > 0) {
            // Loger.log("预加载资源:", JSON.stringify(preloadList));
            await this._loadResArray(preloadList);
        }
    }

    private _getLoadedRes<T extends cc.Asset>(path: string,type = null,bundleName: string = "resources"): T {
        let res = ResUtils.getRes<T>(path,type,bundleName)
        if (res) {
            let assetInfo = this.dependAssetMap[res.uuid]
            if (assetInfo) {
                assetInfo[1] += 1
            } else {
                res.addRef();
                this.dependAssetMap[res.uuid] = [res, 1]
            }
            return res as T
        }
        return null;
    }

    private _loadRes<T extends cc.Asset>(path: string,type = null, bundleName: string = "resources"): Promise<T> {
        return new Promise<T>((resolve, reject) => {
            ResUtils.loadRes<T>(path, type,bundleName).then((res: T) => {
                if (!this.node || !this.node.isValid) {
                    if (res) {
                        res.decRef();
                    }
                    reject("节点不存在,或已释放")
                    return;
                }
                if (!res) {
                    reject("资源不存在,或已释放")
                    return;
                }
                this._cacheRes(res);
                resolve(res as T);
            }).catch((err: Error) => {
                reject(err)
            })
        })
    }

    private _loadResArray<T extends cc.Asset>(path: string[], bundleName: string = "resources"): Promise<T[]> {
        return new Promise<T[]>((resolve, reject) => {
            ResUtils.loadResArray<T>(path,bundleName).then((res: T[]) => {
                if (!this.node || !this.node.isValid) {
                    if (res) {
                        for (let index = 0; index < res.length; index++) {
                            const one = res[index];
                            one.decRef();
                        }
                    }
                    reject("节点不存在,或已释放")
                    return;
                }
                if (!res) {
                    reject("资源不存在,或已释放")
                    return;
                }
                this._cacheRes(res);
                resolve(res as T[]);
            }).catch((err: Error) => {
                reject(err)
            })
        })
    }


    /**
     * 真实引用计数每个控制器只持有一份，具体使用计数用自身计数器,控制器统一管理内存
     * @param cc.AssetArray 缓存动态加载的资源
     */
    private _cacheRes(assetArray: cc.Asset | cc.Asset[]) {
        if (!(assetArray instanceof Array)) {
            assetArray = [assetArray]
        }
        for (let index = 0; index < assetArray.length; index++) {
            let asset: cc.Asset = assetArray[index];
            let assetInfo = this.dependAssetMap[asset.uuid]
            if (assetInfo) {
                assetInfo[1] += 1
            } else {
                asset.addRef()
                this.dependAssetMap[asset.uuid] = [asset, 1]
            }
        }
    }

    /**
     * 释放缓存的资源
     */
    releaseCache() {
        Loger.log("release controller cache:", this.node.name);
        for (const key in this.dependAssetMap) {
            const assetInfo = this.dependAssetMap[key];
            let asset: cc.Asset = assetInfo[0]
            asset.decRef();
        }
        this.dependAssetMap = {};
    }


    /**
     * 手动释放资源
     * @param res 需要释放的资源
     * 适当的时候可以调用removeUnusedRes方法对count == 0的资源进行集中释放，防止资源反复加载卸载
     */
    releaseRes(res: cc.Asset | cc.Asset[]) {
        let resList: any[] = []
        if (res instanceof Array) {
            resList = res
        } else {
            resList.push(res)
        }
        for (const asset of resList) {
            if (asset) {
                let assetInfo = this.dependAssetMap[asset.uuid]
                if (assetInfo) {
                    let count = (--assetInfo[1])
                    let asset: cc.Asset = assetInfo[0]
                    if (count <= 0) {
                        asset.decRef()
                        delete this.dependAssetMap[asset.uuid]
                    }
                }
            }
        }
    }

    /**
     * 移除无用资源
     */
    removeUnusedRes() {
        for (const key in this.dependAssetMap) {
            const assetInfo = this.dependAssetMap[key];
            let count = assetInfo[1]
            let asset: cc.Asset = assetInfo[0]
            if (count <= 0) {
                delete this.dependAssetMap[asset.uuid]
                asset.decRef()
            }
        }
    }
}
